// Based on src/kml/convenience/gpx_trk_pt_handler_test.cc

// Copyright 2008, Google Inc. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without 
// modification, are permitted provided that the following conditions are met:
//
//  1. Redistributions of source code must retain the above copyright notice, 
//     this list of conditions and the following disclaimer.
//  2. Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//  3. Neither the name of Google Inc. nor the names of its contributors may be
//     used to endorse or promote products derived from this software without
//     specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// This file contains the unit tests for the GpxTrkPtHandler class.

#include "gpx_parser.h"
#include <sstream>
#include <vector>
#include "boost/scoped_ptr.hpp"
#include "expat_parser.h"
#include "gps.pb.h"
#include "gtest/gtest.h"
#include "string_util.h"

#if 0
#ifndef DATADIR
#error *** DATADIR must be defined! ***
#endif
#endif

namespace gps_proto {

class GpxParserTest : public testing::Test {
 protected:
  void SetUp() {
    gpx_parser_.reset(new GpxParser(&gps_proto_));
  }

  GpsProto gps_proto_;
  boost::scoped_ptr<GpxParser> gpx_parser_;
};


TEST_F(GpxParserTest, ParseGpx) {
  gpx_parser_->StartElement("gpx", NULL);
  gpx_parser_->EndElement("gpx");
}

TEST_F(GpxParserTest, ParseGpxTrk) {
  gpx_parser_->StartElement("gpx", NULL);
  gpx_parser_->StartElement("trk", NULL);
  gpx_parser_->EndElement("trk");
  gpx_parser_->EndElement("gpx");
  ASSERT_EQ(1, gps_proto_.track_size());
}

TEST_F(GpxParserTest, ParseGpxTrkTrkseg) {
  gpx_parser_->StartElement("gpx", NULL);
  gpx_parser_->StartElement("trk", NULL);
  gpx_parser_->StartElement("trkseg", NULL);
  gpx_parser_->EndElement("trkseg");
  gpx_parser_->EndElement("trk");
  gpx_parser_->EndElement("gpx");
  ASSERT_EQ(1, gps_proto_.track_size());
  ASSERT_EQ(1, gps_proto_.track(0).segment_size());
}

TEST_F(GpxParserTest, ParseGpxTrkTrksegTrkpt) {
  gpx_parser_->StartElement("gpx", NULL);
  gpx_parser_->StartElement("trk", NULL);
  gpx_parser_->StartElement("trkseg", NULL);
  gpx_parser_->StartElement("trkpt", NULL);
  gpx_parser_->EndElement("trkpt");
  gpx_parser_->EndElement("trkseg");
  gpx_parser_->EndElement("trk");
  gpx_parser_->EndElement("gpx");
  ASSERT_EQ(1, gps_proto_.track_size());
  ASSERT_EQ(1, gps_proto_.track(0).segment_size());
  ASSERT_EQ(1, gps_proto_.track(0).segment(0).point_size());
}

TEST_F(GpxParserTest, ParseGpxTrkTrksegTrkptEle) {
  gpx_parser_->StartElement("gpx", NULL);
  gpx_parser_->StartElement("trk", NULL);
  ASSERT_EQ(1, gps_proto_.track_size());
  gpx_parser_->StartElement("trkseg", NULL);
  ASSERT_EQ(1, gps_proto_.track(0).segment_size());
  gpx_parser_->StartElement("trkpt", NULL);
  ASSERT_EQ(1, gps_proto_.track(0).segment(0).point_size());
  gpx_parser_->StartElement("ele", NULL);
  const char* kEle("134.7219238");
  gpx_parser_->CharData(kEle, strlen(kEle));
  gpx_parser_->EndElement("ele");
  ASSERT_TRUE(gps_proto_.track(0).segment(0).point(0).has_ele());
  ASSERT_EQ(134.7219238,
            gps_proto_.track(0).segment(0).point(0).ele());
  gpx_parser_->EndElement("trkpt");
  gpx_parser_->EndElement("trkseg");
  gpx_parser_->EndElement("trk");
  gpx_parser_->EndElement("gpx");
}

TEST_F(GpxParserTest, ParseGpxTrkTrksegTrkptLatLon) {
  gpx_parser_->StartElement("gpx", NULL);
  gpx_parser_->StartElement("trk", NULL);
  gpx_parser_->StartElement("trkseg", NULL);
  const char* attrs[] = { "lat", "-123.456", "lon", "37.37", NULL };
  gpx_parser_->StartElement("trkpt", attrs);
  gpx_parser_->EndElement("trkpt");
  gpx_parser_->EndElement("trkseg");
  gpx_parser_->EndElement("trk");
  gpx_parser_->EndElement("gpx");
  ASSERT_EQ(1, gps_proto_.track_size());
  ASSERT_EQ(1, gps_proto_.track(0).segment_size());
  ASSERT_EQ(1, gps_proto_.track(0).segment(0).point_size());
  const PointProto& point = gps_proto_.track(0).segment(0).point(0);
  ASSERT_EQ(-123.456, point.lat());
  ASSERT_EQ(37.37, point.lon());
}

TEST_F(GpxParserTest, ParseGpxTrkTrksegTrkptLatLonEle) {
  gpx_parser_->StartElement("gpx", NULL);  // <gpx>
  gpx_parser_->StartElement("trk", NULL);  // <trk>
  gpx_parser_->StartElement("trkseg", NULL);  // <trkseg>
  // <trkpt lat="..." lon="...">
  const char* attrs[] = { "lat", "-123.456", "lon", "37.37", NULL };
  gpx_parser_->StartElement("trkpt", attrs);
  gpx_parser_->StartElement("ele", NULL);  // <ele>
  const char* kEle("1234.56789");
  gpx_parser_->CharData(kEle, strlen(kEle));
  gpx_parser_->EndElement("ele");  // </ele>
  gpx_parser_->EndElement("trkpt");  // </trkpt>
  gpx_parser_->EndElement("trkseg");  // </trkseg>
  gpx_parser_->EndElement("trk");  // </trk>
  gpx_parser_->EndElement("gpx");  // </gpx>
  ASSERT_EQ(1, gps_proto_.track_size());
  ASSERT_EQ(1, gps_proto_.track(0).segment_size());
  ASSERT_EQ(1, gps_proto_.track(0).segment(0).point_size());
  const PointProto& point = gps_proto_.track(0).segment(0).point(0);
  ASSERT_EQ(-123.456, point.lat());
  ASSERT_EQ(37.37, point.lon());
  ASSERT_EQ(1234.56789, point.ele());
}

TEST_F(GpxParserTest, ParseGpxTrkTrksegTrkptLatLonEleTime) {
  gpx_parser_->StartElement("gpx", NULL);  // <gpx>
  gpx_parser_->StartElement("trk", NULL);  // <trk>
  gpx_parser_->StartElement("trkseg", NULL);  // <trkseg>
  // <trkpt lat="..." lon="...">
  const char* attrs[] = { "lat", "-123.456", "lon", "37.37", NULL };
  gpx_parser_->StartElement("trkpt", attrs);
  gpx_parser_->StartElement("ele", NULL);  // <ele>
  const char* kEle("1234.56789");
  gpx_parser_->CharData(kEle, strlen(kEle));
  gpx_parser_->EndElement("ele");  // </ele>
  // <time>2008-10-03T11:10:01Z</time>
  gpx_parser_->StartElement("time", NULL);
  const std::string kTime("2008-10-03T11:10:01Z");
  gpx_parser_->CharData(kTime.c_str(), kTime.size());
  gpx_parser_->EndElement("time");  // </time>
  gpx_parser_->EndElement("trkpt");  // </trkpt>
  gpx_parser_->EndElement("trkseg");  // </trkseg>
  gpx_parser_->EndElement("trk");  // </trk>
  gpx_parser_->EndElement("gpx");  // </gpx>
  ASSERT_EQ(1, gps_proto_.track_size());
  ASSERT_EQ(1, gps_proto_.track(0).segment_size());
  ASSERT_EQ(1, gps_proto_.track(0).segment(0).point_size());
  const PointProto& point = gps_proto_.track(0).segment(0).point(0);
  ASSERT_EQ(-123.456, point.lat());
  ASSERT_EQ(37.37, point.lon());
  ASSERT_EQ(1234.56789, point.ele());
  ASSERT_EQ(1223032201, point.time());
}

TEST_F(GpxParserTest, SimpleUseParseGpx) {
  ASSERT_TRUE(GpxParser::ParseFromString("<gpx><trk/></gpx>", &gps_proto_));
  ASSERT_EQ(1, gps_proto_.track_size());
}

TEST_F(GpxParserTest, SimpleUseParseFromIstream) {
  const std::string gpx("<gpx><trk><trkseg><trkpt>"
                        "<ele>1</ele>"
                        "</trkpt></trkseg></trk></gpx>");
  std::istringstream gpx_stream(gpx);
  ASSERT_TRUE(GpxParser::ParseFromIstream(&gpx_stream, &gps_proto_));
  ASSERT_EQ(1, gps_proto_.track_size());
}

TEST_F(GpxParserTest, ParseManyTrkptsFromIstream) {
  const size_t kCount(1001);
  std::string gpx = "<gpx><trk><trkseg>";
  for (size_t i = 0; i < kCount; ++i) {
    gpx.append(std::string("<trkpt lat='") + ToString(i) + "' lon='" +
               ToString(i) + "'/>");
  }
  gpx.append("</trkseg></trk></gpx>");
  std::istringstream gpx_stream(gpx);
  ASSERT_TRUE(GpxParser::ParseFromIstream(&gpx_stream, &gps_proto_));
  ASSERT_EQ(1, gps_proto_.track_size());
  ASSERT_EQ(1, gps_proto_.track(0).segment_size());
  ASSERT_EQ(kCount, gps_proto_.track(0).segment(0).point_size());
  for (size_t i = 0; i < kCount; ++i) {
    const PointProto& point = gps_proto_.track(0).segment(0).point(i);
    ASSERT_EQ(static_cast<double>(i), point.lat());
    ASSERT_EQ(static_cast<double>(i), point.lon());
  }
}

/*
<wpt lat="37.259299932" lon="-122.031999948">
  <ele>8.296997</ele>
  <name>SARATOGA</name>
  <cmt>22-AUG-09</cmt>
  <desc>22-AUG-09</desc>
  <sym>Flag, Blue</sym>
</wpt>
*/

TEST_F(GpxParserTest, TestParseWpt) {
  const std::string gpx("<gpx>"
                        "<wpt lat='37.259299932' lon='-122.031999948'>"
                        "<ele>8.296997</ele>"
                        "<name>SARATOGA</name>"
                        "</wpt>"
                        "</gpx>");
  std::istringstream gpx_stream(gpx);
  ASSERT_TRUE(GpxParser::ParseFromIstream(&gpx_stream, &gps_proto_));
  ASSERT_EQ(1, gps_proto_.waypoint_size());
  const PointProto& waypoint = gps_proto_.waypoint(0);
  EXPECT_TRUE(waypoint.has_lat());
  EXPECT_EQ(37.259299932, waypoint.lat());
  EXPECT_TRUE(waypoint.has_lon());
  EXPECT_EQ(-122.031999948, waypoint.lon());
  EXPECT_TRUE(waypoint.has_ele());
  EXPECT_EQ(8.296997, waypoint.ele());
  EXPECT_TRUE(waypoint.has_name());
  EXPECT_EQ(std::string("SARATOGA"), waypoint.name());
}

#if 0
/*
<trkpt lat="45.987064233" lon="7.714510309">
  <ele>2518.310791</ele>
<time>2007-12-15T12:57:27Z</time>
</trkpt>
<trkpt lat="45.986975972" lon="7.714555487">
  <ele>2525.520752</ele>
<time>2007-12-15T12:57:30Z</time>
</trkpt>
*/

TEST_F(GpxParserTest, TestDiffEncodeDecodeDouble) {
  const double base = 45.987064233;
  const double next = 45.986975972;
  const int diff = GpxParser::DiffEncodeDouble(base, next);
  EXPECT_EQ(-88261, diff);
  EXPECT_EQ(next, GpxParser::DiffDecodeDouble(base, diff));
}

/*
<trkpt lat="38.200803995" lon="-120.367292706">
  <ele>1024.422119</ele>
<time>2008-03-10T00:19:25Z</time>
</trkpt>
<trkpt lat="38.199737901" lon="-120.365858646">
  <ele>1025.383301</ele>
<time>2008-03-10T00:19:33Z</time>
</trkpt>
<trkpt lat="38.199106827" lon="-120.365479533">
  <ele>1024.422119</ele>
<time>2008-03-10T00:19:37Z</time>
</trkpt>
*/

TEST_F(GpxParserTest, TestDiffEncodeTrkSeg) {
  TrkSegProto segment;
  PointProto* trkpt = segment.add_trkpt();
  trkpt->set_lat(38.200803995);
  trkpt->set_lon(-120.367292706);
  // TODO: <ele>, <time>
  trkpt = segment.add_trkpt();
  trkpt->set_lat(38.199737901);
  trkpt->set_lon(-120.365858646);
  trkpt = segment.add_trkpt();
  trkpt->set_lat(38.199106827);
  trkpt->set_lon(-120.365479533);
  GpxParser::DiffEncodeTrkSeg(&segment);
  // No points added or removed.
  ASSERT_EQ(3, segment.trkpt_size());
  // 0th point remains unchanged
  EXPECT_TRUE(segment.trkpt(0).has_lat());
  EXPECT_TRUE(segment.trkpt(0).has_lon());
  EXPECT_EQ(38.200803995, segment.trkpt(0).lat());
  EXPECT_EQ(-120.367292706, segment.trkpt(0).lon());
  // 2th point is differentially encoded
  const PointProto& point2 = segment.trkpt(2);
  EXPECT_FALSE(point2.has_lat());
  EXPECT_FALSE(point2.has_lon());
  EXPECT_TRUE(point2.has_lat_diff());
  EXPECT_TRUE(point2.has_lon_diff());
  EXPECT_EQ(-631074, point2.lat_diff());
  EXPECT_EQ(379113, point2.lon_diff());
}

TEST_F(GpxParserTest, TestDiffDencodeTrkSeg) {
  TrkSegProto segment;
  PointProto* trkpt = segment.add_trkpt();
  trkpt->set_lat(38.200803995);
  trkpt->set_lon(-120.367292706);
  trkpt = segment.add_trkpt();
  trkpt->set_lat(38.199737901);
  trkpt->set_lon(-120.365858646);
  trkpt = segment.add_trkpt();
  trkpt->set_lat_diff(-631074);
  trkpt->set_lon_diff(379113);
  GpxParser::DiffDecodeTrkSeg(&segment);
  const PointProto& point2 = segment.trkpt(2);
  EXPECT_TRUE(point2.has_lat());
  EXPECT_TRUE(point2.has_lon());
  EXPECT_FALSE(point2.has_lat_diff());
  EXPECT_FALSE(point2.has_lon_diff());
  EXPECT_EQ(38.199106827, point2.lat());
  EXPECT_EQ(-120.365479533, point2.lon());
}
#endif

#if 0
// does not cause that first point's time to be repeated.
TEST_F(GpxTrkPtHandlerTest, TestMissingTime) {
  PointVector point_vector;
  TestGpxTrkPtHandler test_gpx_trk_pt_handler(&point_vector);

  // Send down one <trkpt> with a <time> child.
  const char* trkpt0[] = { "lat", "-123.456", "lon", "37.37", NULL };
  test_gpx_trk_pt_handler.StartElement("trkpt", trkpt0);
  // <time>2008-10-03T11:10:01Z</time>
  test_gpx_trk_pt_handler.StartElement("time", NULL);
  const std::string kTime("2008-10-03T11:10:01Z");
  test_gpx_trk_pt_handler.CharData(kTime.c_str(), kTime.size());
  test_gpx_trk_pt_handler.EndElement("time");
  test_gpx_trk_pt_handler.EndElement("trkpt");

  // Send down another <trkpt> with no <time> child.
  const char* trkpt1[] = { "lat", "123.456", "lon", "-37.37", NULL };
  test_gpx_trk_pt_handler.StartElement("trkpt", trkpt1);
  test_gpx_trk_pt_handler.EndElement("trkpt");

  ASSERT_EQ(static_cast<size_t>(2), point_vector.size());
  ASSERT_EQ(-123.456, point_vector[0].first.get_latitude());
  ASSERT_EQ(37.37, point_vector[0].first.get_longitude());
  ASSERT_EQ(kTime, point_vector[0].second);

  ASSERT_EQ(123.456, point_vector[1].first.get_latitude());
  ASSERT_EQ(-37.37, point_vector[1].first.get_longitude());
  // This is the key assertion of this test.
  ASSERT_TRUE(point_vector[1].second.empty());
}

// These are some expected values from testdata/gpx/trkpts.gpx.
static const struct {
  const size_t index;
  const double latitude;
  const double longitude;
  const double altitude;
  const char* time;
} kTrkPtsFileData[] = {
  { 0, 39.235658487, -106.315917922, 3012.428223, "2007-09-16T19:22:00Z" },
  { 1, 39.235505015, -106.316187400, 3011.467285, "2007-09-16T19:22:03Z" },
  { 141, 39.251128044, -106.287899902, 3125.864258, "2007-09-16T19:50:18Z" },
  { 142, 39.251178671, -106.287928736, 3125.864258, "2007-09-16T19:50:35Z" }
};

// Verify overall usage of GpxTrkPtHandler on a real-world GPX file.
TEST_F(GpxTrkPtHandlerTest, TestTrkPtFile) {
  // Read the GPX file contents.
  std::string gpx_data;
  ASSERT_TRUE(kmlbase::File::ReadFileToString(
      std::string(DATADIR) + "/gpx/trkpts.gpx", &gpx_data));
  PointVector point_vector;
  TestGpxTrkPtHandler test_gpx_trk_pt_handler(&point_vector);
  std::string errors;
  ASSERT_TRUE(kmlbase::ExpatParser::ParseString(
        gpx_data, &test_gpx_trk_pt_handler, &errors, false));
  ASSERT_TRUE(errors.empty());
  ASSERT_EQ(static_cast<size_t>(143), point_vector.size());
  size_t size = sizeof(kTrkPtsFileData)/sizeof(kTrkPtsFileData[0]);
  for (size_t i = 0; i < size; ++i) {
    size_t index = kTrkPtsFileData[i].index;
    ASSERT_EQ(kTrkPtsFileData[i].latitude,
                         point_vector[index].first.get_latitude());
    ASSERT_EQ(kTrkPtsFileData[i].longitude,
                         point_vector[index].first.get_longitude());
    ASSERT_EQ(kTrkPtsFileData[i].altitude,
                         point_vector[index].first.get_altitude());
    ASSERT_EQ(std::string(kTrkPtsFileData[i].time),
                         point_vector[index].second);
  }
}
#endif

}  // namespace kmlconvenience

int main(int argc, char** argv) {
  testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}
